﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using EtoolTech.MongoDB.Mapper.Exceptions;
using EtoolTech.MongoDB.Mapper.Interfaces;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Driver;
using MongoDB.Driver.Builders;
using MongoDB.Driver.Wrappers;

namespace EtoolTech.MongoDB.Mapper.Core
{
    public class Finder : IFinder
    {        
        #region Find Methods

        public T FindById<T>(Guid id)
        {
            QueryComplete query = Query.EQ("_id", id);
            return Helper.GetCollection(typeof (T).Name).FindOneAs<T>(query);
        }

        public BsonDocument FindBsonDocumentById<T>(Guid id)
        {
            QueryComplete query = Query.EQ("_id", id);
            return Helper.GetCollection(typeof (T).Name).FindOneAs<T>(query).ToBsonDocument();
        }

        public T FindByKey<T>(params object[] values)
        {
            List<string> fields = Helper.GetPrimaryKey(typeof (T)).ToList();
            var keyValues = new Dictionary<string, object>();
            for (int i = 0; i < fields.Count; i++)
            {
                keyValues.Add(fields[i], values[i]);
            }

            return FindObjectByKey<T>(keyValues);

        }

        public T FindObjectByKey<T>(Dictionary<string, object> keyValues)
        {
            var queryList = new List<QueryComplete>();
            foreach (var keyValue in keyValues)
            {
                if (keyValue.Value.GetType() == typeof(int))
                    queryList.Add(GetEqQuery(typeof(int), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof(string))
                    queryList.Add(GetEqQuery(typeof(string), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof(DateTime))
                    queryList.Add(GetEqQuery(typeof(DateTime), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof(long))
                    queryList.Add(GetEqQuery(typeof(long), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof(bool))
                    queryList.Add(GetEqQuery(typeof(bool), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof(double))
                    queryList.Add(GetEqQuery(typeof(double), keyValue.Key, keyValue.Value));
            }

            QueryComplete query = Query.And(queryList.ToArray());

            MongoCursor<T> result = Helper.GetCollection(typeof(T).Name).FindAs<T>(query);
            if (result.Count() == 0)
                throw  new FindByKeyNotFoundException();
            return result.First();
        }

        public Guid FindGuidByKey<T>(Dictionary<string, object> keyValues)
        {
            var queryList = new List<QueryComplete>();
            foreach (var keyValue in keyValues)
            {
                if (keyValue.Value.GetType() == typeof (int))
                    queryList.Add(GetEqQuery(typeof (int), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof (string))
                    queryList.Add(GetEqQuery(typeof (string), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof (DateTime))
                    queryList.Add(GetEqQuery(typeof (DateTime), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof (long))
                    queryList.Add(GetEqQuery(typeof (long), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof (bool))
                    queryList.Add(GetEqQuery(typeof (bool), keyValue.Key, keyValue.Value));

                if (keyValue.Value.GetType() == typeof (double))
                    queryList.Add(GetEqQuery(typeof (double), keyValue.Key, keyValue.Value));
            }

            QueryComplete query = Query.And(queryList.ToArray());

            MongoCursor<T> result = Helper.GetCollection(typeof (T).Name).FindAs<T>(query);
            if (result.Count() == 0)
                return Guid.Empty;

            object oId;
            IIdGenerator iGen;
            result.First().ToBsonDocument().GetDocumentId(out oId, out iGen);
            return (Guid) oId;
        }

        public List<T> FindAnd<T>(List<IFindOptions> findOptions)
        {
            var queryAnd = Query.And(findOptions.Select(FindOption => GetQuery(FindOption.FindCondition, FindOption.Value, FindOption.FieldName)).ToArray());
            return Helper.GetCollection(typeof(T).Name).FindAs<T>(queryAnd).ToList();
        }

        public List<T> FindOr<T>(List<IFindOptions> findOptions)
        {
            var queryOr = Query.Or(findOptions.Select(FindOption => GetQuery(FindOption.FindCondition, FindOption.Value, FindOption.FieldName)).ToArray());
            return Helper.GetCollection(typeof(T).Name).FindAs<T>(queryOr).ToList();
        }

        public List<T> Find<T>(string field, object value, FindCondition findCondition = FindCondition.Equal)
        {
            QueryComplete query = null;
            query = GetQuery(findCondition, value, field);
            return Helper.GetCollection(typeof (T).Name).FindAs<T>(query).ToList();
        }
      

        public List<T> Find<T>(IFindOptions findOptions)
        {
            return Find<T>(findOptions.FieldName, findOptions.Value, findOptions.FindCondition = FindCondition.Equal);
        }

        //TODO: Pendiente de probar
        public List<T> Find<T>(Expression<Func<T, object>> exp)
        {
            ExpressionParser ep = new ExpressionParser();
            var query = ep.ParseExpression(exp);
            return Helper.GetCollection(typeof (T).Name).FindAs<T>(query).ToList();
        }

        #endregion

        #region GetQuerys

        public QueryComplete GetQuery(FindCondition findCondition, object value, string field)
        {
            QueryComplete query = null;

            if (value.GetType() == typeof(string) && findCondition == FindCondition.Equal)
            {
                bool IsLike = false;
                string txtValue = value.ToString();
                if (txtValue.StartsWith("%") && txtValue.EndsWith("%"))
                {
                    value = "/" + txtValue + "/";
                    IsLike = true;
                }
                else if (txtValue.StartsWith("%"))
                {
                    value = "/" + txtValue + "^/";
                    IsLike = true;
                }
                else if (txtValue.EndsWith("%"))
                {
                    value = "/^" + txtValue + "/";
                    IsLike = true;
                }

                if (IsLike)
                {
                    query = GetRegEx(field, value.ToString());
                    return query;
                }
            }

            switch (findCondition)
            {
                case FindCondition.Equal:
                    query = GetEqQuery(value.GetType(), field, value);
                    break;
                case FindCondition.Greater:
                    query = GetGtQuery(value.GetType(), field, value);
                    break;
                case FindCondition.GreaterOrEqual:
                    query = GetGteQuery(value.GetType(), field, value);
                    break;
                case FindCondition.Lees:
                    query = GetLtQuery(value.GetType(), field, value);
                    break;
                case FindCondition.LessOrEqueal:
                    query = GetLteQuery(value.GetType(), field, value);
                    break;
                case FindCondition.NotEquals:
                    query = GetNeQuery(value.GetType(), field, value);
                    break;
                case FindCondition.RegEx:
                    query = GetRegEx(field, value.ToString());
                    break;
            }
            return query;
        }

        public QueryComplete GetEqQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.EQ(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.EQ(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.EQ(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.EQ(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.EQ(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.EQ(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetGtQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.GT(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.GT(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.GT(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.GT(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.GT(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.GT(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetGteQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.GTE(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.GTE(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.GTE(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.GTE(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.GTE(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.GTE(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetLtQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.LT(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.LT(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.LT(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.LT(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.LT(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.LT(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetLteQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.LTE(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.LTE(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.LTE(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.LTE(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.LTE(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.LTE(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetNeQuery(Type type, string fieldName, object value)
        {
            QueryComplete query = null;

            if (type == typeof (DateTime))
                query = Query.NE(fieldName, (DateTime) value);
            else if (type == typeof (int))
                query = Query.NE(fieldName, (int) value);
            else if (type == typeof (string))
                query = Query.NE(fieldName, (string) value);
            else if (type == typeof (long))
                query = Query.NE(fieldName, (long) value);
            else if (type == typeof (bool))
                query = Query.NE(fieldName, (bool) value);
            else if (type == typeof (double))
                query = Query.NE(fieldName, (double) value);

            return query;
        }

        public QueryComplete GetRegEx(string fieldName, string expresion)
        {
            return Query.Matches(fieldName, expresion);
        }

        #endregion

        #region IFinder Members

     

        public List<T> All<T>()
        {
            return Helper.GetCollection(typeof (T).Name).FindAllAs<T>().ToList();
        }

        #endregion
    }
}